using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;

using Microsoft.FSharp;

namespace Boids_Visualization_XNA {


    struct BoidRenderData {
        public Geom geom;
        public Texture2D texture;
        public Vector2 origin;
    }

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class BoidVisualizerXNA : Microsoft.Xna.Framework.Game {

        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        PhysicsSimulator physicsSimulator;
        Microsoft.FSharp.Collections.List<Boids.Boid> boidList;
        List<BoidRenderData> brenderList;
        Boids.BoidSettings boidSettings;
        Texture2D buffalo;

        public BoidVisualizerXNA() {
            this.Content.RootDirectory = "Content";
            this.TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 10);
            //this.Window.AllowUserResizing = true;

            this.graphics = new GraphicsDeviceManager(this);
            this.graphics.PreferredBackBufferWidth = 1024;
            this.graphics.PreferredBackBufferHeight = 768;
            this.Window.AllowUserResizing = true;

            this.physicsSimulator = new PhysicsSimulator(new Vector2(0, 0));
            this.brenderList = new List<BoidRenderData>();
        }


        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent() {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            IGraphicsDeviceService graphicsService = (IGraphicsDeviceService)this.Services.GetService(typeof(IGraphicsDeviceService));

            Single width = Convert.ToSingle(graphicsService.GraphicsDevice.Viewport.Width);
            Single height = Convert.ToSingle(graphicsService.GraphicsDevice.Viewport.Height);
            Single wall = 50;

            Single boid_max_x = width - wall;
            Single boid_max_y = height - wall;
            Single boid_min = wall;

            Boids.WanderSettings ws = new Boids.WanderSettings();
            ws.change = .7f;
            ws.wander_dist = 80f;
            ws.wander_radius = 40f;

            this.buffalo = Content.Load<Texture2D>("buffalo");
            Vector2 bvector = new Vector2(this.buffalo.Width / 2f, this.buffalo.Height / 2f);

            this.boidSettings = new Boids.BoidSettings();
            this.boidSettings.width = width;
            this.boidSettings.height = height;
            this.boidSettings.wall = wall;
            this.boidSettings.wall_force = 50f;
            this.boidSettings.max_vel = 800f;  //max vel
            this.boidSettings.cohesion_mult = .1f;
            this.boidSettings.scatter_mult = 0f;
            this.boidSettings.separation_mult = .1f;
            this.boidSettings.align_mult = .01f;
            this.boidSettings.range = 30f;
            this.boidSettings.steer_force = 15f;
            this.boidSettings.mass = 1f;
            this.boidSettings.rotation_drag = 7f;
            this.boidSettings.linear_drag = .5f;
            this.boidSettings.size = new Vector2(this.buffalo.Width, this.buffalo.Height);
            this.boidSettings.moment_inertia = (this.boidSettings.mass * (this.buffalo.Width * this.buffalo.Width + this.buffalo.Height * this.buffalo.Height)) / 12;
            this.boidSettings.wander_settings = ws;

            this.boidList = Boids.Utilities.generate_boid_list(30, this.physicsSimulator, this.boidSettings);
            
            foreach (Boids.Boid b in this.boidList) {
                BoidRenderData brd = new BoidRenderData();
                brd.geom = GeomFactory.Instance.CreateCircleGeom(this.physicsSimulator, b, this.buffalo.Width / 2f, Convert.ToInt32(this.buffalo.Height));
                //brd.geom = GeomFactory.Instance.CreateRectangleGeom(this.physicsSimulator, b, this.buffalo.Width, this.buffalo.Height);
                brd.geom.RestitutionCoefficient = .01f;
                brd.texture = this.buffalo;
                brd.origin = bvector;
                this.brenderList.Add(brd);
            }
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent() {
            this.physicsSimulator.Clear();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime) {
            if (IsActive) {
                //if (gameTime.TotalRealTime.Seconds % 5 == 0) {
                //    this.boidSettings.align_mult *= -1;
                //}
                physicsSimulator.Update(gameTime.ElapsedGameTime.Milliseconds * .001f);
                Boids.Rules.boid_list_apply_rules(this.boidList, this.boidSettings);
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime) {
            this.graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
            this.spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

            foreach (BoidRenderData brd in this.brenderList) {
                this.spriteBatch.Draw(brd.texture, brd.geom.Position, null, Color.White, brd.geom.Rotation, brd.origin, 1, SpriteEffects.None, 0);
            }

            this.spriteBatch.End();
        }

    }
}
